package org.xx.armory.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.db.DbContext;
import org.xx.armory.db.SessionFactory;
import org.xx.armory.security.KeyStorage;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.xx.armory.commons.CryptographicUtils.decryptWithBase64;
import static org.xx.armory.commons.CryptographicUtils.encryptWithBase64;
import static org.xx.armory.commons.Validators.notBlank;
import static org.xx.armory.commons.Validators.notNull;

/**
 * 所有的服务的抽象基类。
 *
 * @author Haart
 */
public abstract class AbstractService
        implements DbContext {
    private static final String SENSITIVE_KEY_NAME = "sensitive";
    private final Logger logger = LoggerFactory.getLogger(AbstractService.class);
    private final ConcurrentMap<Class<?>, Object> objectCache;
    private SessionFactory sessionFactory;

    /**
     * 构造服务的抽象基类
     */
    protected AbstractService() {
        objectCache = new ConcurrentHashMap<>();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final SessionFactory getSessionFactory() {
        return this.sessionFactory;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = notNull(sessionFactory, "sessionFactory");
    }

    /**
     * 根据对象的类型获取对象, 此处应当缓存生成的数据访问对象并复用。
     *
     * @param <T>
     *         对象的类型。
     * @param clazz
     *         数据访问对象的类型。
     * @return 指定的数据访问对象。
     * @throws IllegalArgumentException
     *         如果创建指定类型的DAO失败。
     */
    protected final <T> T getObject(
            Class<T> clazz
    ) {
        notNull(clazz, "clazz");
        return clazz.cast(objectCache.computeIfAbsent(clazz, this::createObject));
    }

    /**
     * 根据对象的类型生成对象。
     *
     * @param clazz
     *         对象的类型。
     * @return 生成的对象。
     * @throws IllegalArgumentException
     *         如果创建指定类型的对象失败。
     */
    private Object createObject(
            Class<?> clazz
    ) {
        notNull(clazz, "clazz");

        try {
            final Constructor<?> constructor = clazz.getConstructor();
            if (constructor == null) {
                throw new IllegalArgumentException("cannot find default constructor " + clazz + ".<init>(V)");
            }

            final Object obj = constructor.newInstance();
            if (obj instanceof DbContext) {
                ((DbContext) obj).setSessionFactory(getSessionFactory());
            }

            return obj;
        } catch (ReflectiveOperationException ex) {
            throw new IllegalArgumentException("cannot create object " + clazz, ex);
        }
    }

    /**
     * 加密敏感数据。
     * <p>将明文按照UTF_8编码，然后通过配置中定义的{@literal sensitive}密钥进行加密，最后转为Base64格式的字符串。</p>
     *
     * @param plainText
     *         待加密的明文。
     * @return 加密后的密文，如果参数 {@code plainText}是{@code null}或者是空字符串则返回空字符串。
     */
    protected final String encryptSensitive(
            String plainText
    ) {
        if (plainText == null || plainText.isEmpty()) {
            return "";
        }

        return encryptWithBase64(plainText, UTF_8, KeyStorage.getInstance().getDESKey(SENSITIVE_KEY_NAME));
    }

    /**
     * 解密敏感数据。
     * <p>将Base64格式的密文转为字节数组，然后解密，最后按照UTF_8解码。</p>
     *
     * @param cipherText
     *         待解密的密文。
     * @return 解密后的明文，如果参数 {@code cipherText}是{@code null}或者是空字符串则返回空字符串。
     */
    protected final String decryptSensitive(
            String cipherText
    ) {
        if (cipherText == null || cipherText.isEmpty()) {
            return "";
        }

        try {
            return decryptWithBase64(cipherText, UTF_8, KeyStorage.getInstance().getDESKey(SENSITIVE_KEY_NAME));
        } catch (Exception ex) {
            logger.warn("cannot decrypt sensitive data", ex);
            return "*** hidden ***";
        }
    }

    /**
     * 设置指定的Bean的字段的值。
     *
     * @param bean
     *         待设置属性的Bean。
     * @param propertyName
     *         属性的名字，自动去掉首尾空格。
     * @param propertyValue
     *         属性的值。
     * @throws IllegalArgumentException
     *         如果参数{@code propertyName}是{@code null}或者只包含空白字符。
     */
    protected final void setBeanProperty(
            Object bean,
            String propertyName,
            Object propertyValue
    ) {
        if (bean == null) {
            return;
        }
        propertyName = notBlank(propertyName, "propertyName").trim();

        final Class<?> clazz = bean.getClass();

        try {
            logger.debug("set \"{}\" to {}@{}", propertyValue, clazz.getSimpleName(), Integer.toHexString(System.identityHashCode(bean)));

            final Field field = clazz.getDeclaredField(propertyName);
            field.setAccessible(true);
            field.set(bean, propertyValue);
        } catch (ReflectiveOperationException ex) {
            logger.warn("cannot set property", ex);
        }
    }
}
